/*  Copyright (c) 2012 William Rogers and James Boud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/


// TODO:  This is patched to allow Begin Parameters of ...State kind
//        but only by kludging through RenderState - needs work

using System;
using System.Text;
using Color = Microsoft.Xna.Framework.Color;
//TODO: We have no equivalent. Problems? using SpriteBlendMode = Microsoft.Xna.Framework.Graphics.Blend;
//using SpriteBlendMode = Microsoft.Xna.Framework.Graphics.BlendState;
using SpriteSortMode = Microsoft.Xna.Framework.Graphics.SpriteSortMode;
//TODO:Check if commenting this out causes problems (has been commented out in the code below). (XNA 4 has no equivalent, instead you must check all states are set). using SaveStateMode = Microsoft.Xna.Framework.Graphics.SaveStateMode;
//http://forums.create.msdn.com/forums/t/68739.aspx
using SpriteEffects = Microsoft.Xna.Framework.Graphics.SpriteEffects;
using Vector2 = Microsoft.Xna.Framework.Vector2;
using Matrix = Microsoft.Xna.Framework.Matrix;
using Rectangle = Microsoft.Xna.Framework.Rectangle;
using MathHelper = Microsoft.Xna.Framework.MathHelper;
using SlimDX;
using SlimDX.Direct3D11;
using JBBRXG11ContentFBXModel;
using JBBRXG11ContentSpriteFont;
using JBBRXG11ContentTexture;

namespace JBBRXG11
{
    /// <summary>
    /// Enables a group of sprites to be drawn using the same settings.
    /// </summary>
    public class SpriteBatch : IDisposable
    {
        const int batch_buffer_size = 100;    // Number of sprites that can be batched
        static int idnumber = 1;              // To give sprite batches unique names
        const string default_shader_source =  // Shader used for image drawing, can be overridden
        #region ImageShader
"texture2D Texture;" +
"" +
"float4x4 Transform: MATRIX = float4x4(float4(1,0,0,0),float4(0,1,0,0),float4(0,0,1,0),float4(0,0,0,1));" +
"" +
"SamplerState samLinear" +
"{" +
"    Filter = MIN_MAG_MIP_LINEAR;" +
"    AddressU = Clamp;" +
"    AddressV = Clamp;" +
"};" +
"" +
"struct VS_IN" +
"{" +
"    float4 Pos: POSITION;" +
"    float2 TexC: TEXTURECOORD;" +
"    float4 Col: COLOR;" +
"};" +
"" +
"struct PS_IN" +
"{" +
"	 float4 Pos: SV_POSITION;" +
"	 float2 TexC: TEXTURECOORD;" +
"    float4 Col: COLOR;" +
"};" +
"" +
"PS_IN VS( VS_IN input )" +
"{" +
"    PS_IN output;" +
"    output.Pos = mul(input.Pos, Transform);" +
"    output.TexC = input.TexC;" +
"    output.Col = input.Col;" +
"    return output;" +
"}" +
"" +
"float4 PS( PS_IN input ) : SV_Target" +
"{" +
"    float4 colour = Texture.Sample(samLinear, input.TexC) * input.Col;" +
//"    if (colour.a < 0.01) discard;" +
"    return colour;" +
"}" +
"" +
"technique10 RenderSprite" +
"{" +
"    pass PSprite" +
"    {" +
"        SetVertexShader( CompileShader( vs_4_0, VS() ) );" +
"        SetHullShader( NULL );" +
"        SetDomainShader( NULL );" +
"        SetGeometryShader( NULL );" +
"        SetPixelShader( CompileShader( ps_4_0, PS() ) );" +
"    }" +
"}";
        #endregion ImageShader
        const string string_shader_source =   // Shader used for string drawing, overridden???
        #region StringShader
"texture2D Texture;" +
"" +
"float4x4 Transform: MATRIX = float4x4(float4(1,0,0,0),float4(0,1,0,0),float4(0,0,1,0),float4(0,0,0,1));" +
"" +
"SamplerState samLinear" +
"{" +
"    Filter = MIN_MAG_MIP_LINEAR;" +
"    AddressU = Clamp;" +
"    AddressV = Clamp;" +
"};" +
"" +
"struct VS_IN" +
"{" +
"    float4 Pos: POSITION;" +
"    float2 TexC: TEXTURECOORD;" +
"    float4 Col: COLOR;" +
"};" +
"" +
"struct PS_IN" +
"{" +
"	 float4 Pos: SV_POSITION;" +
"	 float2 TexC: TEXTURECOORD;" +
"    float4 Col: COLOR;" +
"};" +
"" +
"PS_IN VS( VS_IN input )" +
"{" +
"    PS_IN output;" +
"    output.Pos = mul(input.Pos, Transform);" +
"    output.TexC = input.TexC;" +
"    output.Col = input.Col;" +
"    return output;" +
"}" +
"" +
"float4 PS( PS_IN input ) : SV_Target" +
"{" +
"    float alpha = Texture.Sample(samLinear, input.TexC).a;" +
//"    if (alpha < 0.01) discard;" +
"    return float4(input.Col.rgb * alpha, alpha);" +
"}" +
"" +
"technique10 RenderSprite" +
"{" +
"    pass PSprite" +
"    {" +
"        SetVertexShader( CompileShader( vs_4_0, VS() ) );" +
"        SetHullShader( NULL );" +
"        SetDomainShader( NULL );" +
"        SetGeometryShader( NULL );" +
"        SetPixelShader( CompileShader( ps_4_0, PS() ) );" +
"    }" +
"}";
        #endregion StringShader
        
        class batchitem : IComparable<batchitem>
        {
            public Texture2D texture;
            public Matrix destxform;
            public Vector2 texpos, texsize;
            public float layerdepth;
            public uint colour;
            public bool isstring, hflip, vflip;

            public int CompareTo(batchitem other)
            {
                int res;
                res = this.isstring.CompareTo(other.isstring);
                if (res == 0)
                {
                    res = this.texture.GetHashCode().CompareTo(other.texture.GetHashCode());
                    if (res == 0)
                        res = this.layerdepth.CompareTo(other.layerdepth);
                }
                return res;
            }
        }

        GraphicsDevice device;
        //int devwidth, devheight;
        float invdevwidth, invdevheight;
        Effect sprite_shader, string_shader;
        SlimDX.Direct3D11.EffectPass dximagepass, dxstringpass;
        VertexDeclaration vdecl;
        SlimDX.Direct3D11.Buffer vertexBuffer, indexBuffer;
        BlendStateDescription additivebsdesc, blendbsdesc, nonebsdesc;

        batchitem[] batch;
        int batchcount;
        DataBox box;  // Used when packing data into vertex buffer

        bool isdisposed;
        string name;
        object tag;

        // Items that have to do with Begin parameters
        Matrix currenttransform;
        SpriteSortMode currentsortmode;
        //SaveStateMode currentsavestatemode;
        // Saved renderstate information (Only save what I set)
        BlendStateDescription savedblendstatedesc;
        DepthStencilStateDescription saveddepthstencilstatedesc;

        public GraphicsDevice GraphicsDevice { get { return device; } }
        public bool IsDisposed { get { return isdisposed; } }
        public string Name { get { return name; } set { name = value; } }
        public object Tag { get { return tag; } set { tag = value; } }

        public SpriteBatch(GraphicsDevice dev)
        {
            device = dev;
            //devwidth = dev.Width;
            //devheight = dev.Height;
            //invdevwidth = 1.0f / (float)(devwidth);
            //invdevheight = 1.0f / (float)(devheight);
            sprite_shader = new Effect(default_shader_source);
            dximagepass = sprite_shader.DX_Effect.GetTechniqueByIndex(0).GetPassByIndex(0);
            string_shader = new Effect(string_shader_source);
            dxstringpass = string_shader.DX_Effect.GetTechniqueByIndex(0).GetPassByIndex(0);
            vdecl = new VertexDeclaration(new Microsoft.Xna.Framework.Graphics.VertexElement[] {
                new Microsoft.Xna.Framework.Graphics.VertexElement(0, Microsoft.Xna.Framework.Graphics.VertexElementFormat.Vector4, Microsoft.Xna.Framework.Graphics.VertexElementUsage.Position, 0),
                new Microsoft.Xna.Framework.Graphics.VertexElement(16, Microsoft.Xna.Framework.Graphics.VertexElementFormat.Vector2, Microsoft.Xna.Framework.Graphics.VertexElementUsage.TextureCoordinate, 0),
                new Microsoft.Xna.Framework.Graphics.VertexElement(24, Microsoft.Xna.Framework.Graphics.VertexElementFormat.Color, Microsoft.Xna.Framework.Graphics.VertexElementUsage.Color, 0),
            });

            vdecl.CreateLayout(device, dximagepass.Description.Signature);
            //TODO: Implement Buffer Constructer changes properly. Last Int is a guess.
            vertexBuffer = new SlimDX.Direct3D11.Buffer(
                    device.DX_Device,
                    4 * 28 * batch_buffer_size,
                    ResourceUsage.Dynamic,
                    BindFlags.VertexBuffer,
                    CpuAccessFlags.Write,
                    ResourceOptionFlags.None, 28
            );
            int[] indexData = new int[6 * batch_buffer_size];
            int i = 0, off = 0;
            for (int b = 0; b < batch_buffer_size; b++)
            {
                indexData[i++] = off + 0;
                indexData[i++] = off + 1;
                indexData[i++] = off + 2;
                indexData[i++] = off + 1;
                indexData[i++] = off + 3;
                indexData[i++] = off + 2;
                off += 4;
            }
            //TODO: Implement Buffer Constructer changes properly. Last Int is a guess.
            indexBuffer = new SlimDX.Direct3D11.Buffer(
                device.DX_Device,
                new DataStream(indexData, true, false),
                6 * 4 * batch_buffer_size,
                ResourceUsage.Immutable,
                BindFlags.IndexBuffer,
                CpuAccessFlags.None,
                ResourceOptionFlags.None, 4);

            // Prepare blend and depthstencil descriptions for the different blending modes

            // Additive
            additivebsdesc = new BlendStateDescription();
            device.RenderState.CopyFromDefaultBlendState(ref additivebsdesc);
            additivebsdesc.RenderTargets[0].BlendEnable = true;// SetBlendEnable(0, true);
            additivebsdesc.RenderTargets[0].BlendOperation = BlendOperation.Add;
            additivebsdesc.RenderTargets[0].SourceBlend = BlendOption.SourceColor;
            additivebsdesc.RenderTargets[0].DestinationBlend = BlendOption.DestinationColor;

            // Blending
            blendbsdesc = new BlendStateDescription();
            device.RenderState.CopyFromDefaultBlendState(ref blendbsdesc);
            blendbsdesc.RenderTargets[0].BlendEnable = true;
            blendbsdesc.RenderTargets[0].BlendOperation = BlendOperation.Add;
            blendbsdesc.RenderTargets[0].SourceBlend = BlendOption.SourceAlpha;
            blendbsdesc.RenderTargets[0].DestinationBlend = BlendOption.InverseSourceAlpha;

            // No blend operation
            nonebsdesc = new BlendStateDescription();
            device.RenderState.CopyFromDefaultBlendState(ref nonebsdesc);

            // Not sure about batching,
            // But, at least the depth sorting does something - order untested
            batch = new batchitem[batch_buffer_size];
            for (int b = 0; b < batch_buffer_size; b++) batch[b] = new batchitem();
            batchcount = 0;
            box = null;

            isdisposed = false;
            name = "SpriteBatch" + idnumber.ToString();
            idnumber++;
            tag = null;
            savedblendstatedesc = new BlendStateDescription();
            GraphicsDeviceManager.GetGame(device).UserContentAdd(this);
        }
        #region BeginOld
        /*
        public void Begin(SpriteBlendMode blendMode, SpriteSortMode sortMode,
                          Matrix transformMatrix)//SaveStateMode stateMode, Matrix transformMatrix)
        {
            // This does not properly deal with sort mode.  I just implement
            // a single, probably incorrect version of Deferred mode.
            currentsortmode = sortMode;
            //currentsavestatemode = stateMode;
            currenttransform = transformMatrix;

            //if (currentsavestatemode == SaveStateMode.SaveState)
            {
                // Just save these two bits as they are they only bits changed by spritebatch
                device.RenderState.CopyFromCurrentBlendState(ref savedblendstatedesc);
                saveddepthstencilstatedesc = device.RenderState.CurrentDepthStencilState;
            }

            DepthStencilStateDescription dssdesc = device.RenderState.CurrentDepthStencilState;
            switch (blendMode)
            {
                // Premultiplied alpha is not included.  It is default in XNA4
                // It is: Use full source and inverse source alpha to weight destination
                case SpriteBlendMode.Additive:
                    //SpriteBlendMode.Additive // Just adds the source and destination
                    device.RenderState.CopyToCurrentBlendState(ref additivebsdesc);

                    // Choose depth buffer settings for transparency
                    dssdesc.DepthWriteMask = DepthWriteMask.Zero;
                    dssdesc.IsDepthEnabled = false;
                    break;
                case SpriteBlendMode.AlphaBlend:
                    //SpriteBlendMode.Alpha    // Uses the source alpha value for blending
                    device.RenderState.CopyToCurrentBlendState(ref blendbsdesc);

                    // Choose depth buffer settings for transparency
                    dssdesc.DepthWriteMask = DepthWriteMask.Zero;
                    dssdesc.IsDepthEnabled = false;
                    break;
                case SpriteBlendMode.Opaque:
                    //SpriteBlendMode.None     // No blending
                    device.RenderState.CopyToCurrentBlendState(ref nonebsdesc);

                    // Choose depth buffer settings for transparency
                    dssdesc.DepthWriteMask = DepthWriteMask.All;
                    dssdesc.IsDepthEnabled = true;
                    break;
            }
            device.RenderState.CurrentDepthStencilState = dssdesc;

            device.DX_Device.InputAssembler.SetPrimitiveTopology(SlimDX.Direct3D11.PrimitiveTopology.TriangleList);
            device.VertexDeclaration = vdecl;
            device.DX_Device.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 28, 0));
            device.DX_Device.InputAssembler.SetIndexBuffer(indexBuffer, SlimDX.DXGI.Format.R32_UInt, 0);
        }

        public void Begin()
        {
            Begin(SpriteBlendMode.Opaque, SpriteSortMode.Deferred, Matrix.Identity);//SaveStateMode.None;
        }

        public void Begin(SpriteBlendMode blendMode)
        {
            Begin(blendMode, SpriteSortMode.Deferred, Matrix.Identity);//SaveStateMode.None, Matrix.Identity);
        }

        public void Begin(SpriteBlendMode blendMode, SpriteSortMode sortMode)//, SaveStateMode stateMode)
        {
            Begin(blendMode, sortMode, Matrix.Identity);//stateMode, Matrix.Identity);
        }*/
        #endregion

        Vector4 transformxy(float vx, float vy, float vz, Matrix m)
        {
            Vector4 res;
            res.X = vx * m.M11 + vy * m.M21 + m.M41;
            res.Y = vx * m.M12 + vy * m.M22 + m.M42;
            res.Z = vz;
            res.W = 1.0f;
            return res;
        }

        void FlushBatchItem(batchitem bi)
        {
            //bi.vflip = false;
            //bi.texsize = new Vector2(0, 0);
            box.Data.Write(transformxy(0, 0, bi.layerdepth, bi.destxform));     // Top Left
            box.Data.Write(new Vector2(bi.texpos.X + (bi.hflip ? bi.texsize.X : 0),
                                    bi.texpos.Y + (bi.vflip ? bi.texsize.Y : 0)));
            box.Data.Write(bi.colour);
            box.Data.Write(transformxy(2, 0, bi.layerdepth, bi.destxform));     // Top Right
            box.Data.Write(new Vector2(bi.texpos.X + (bi.hflip ? 0 : bi.texsize.X),
                                     bi.texpos.Y + (bi.vflip ? bi.texsize.Y : 0)));
            box.Data.Write(bi.colour);
            box.Data.Write(transformxy(0, 2, bi.layerdepth, bi.destxform));     // Bottom Left
            box.Data.Write(new Vector2(bi.texpos.X + (bi.hflip ? bi.texsize.X : 0),
                                     bi.texpos.Y + (bi.vflip ? 0 : bi.texsize.Y)));
            box.Data.Write(bi.colour);
            box.Data.Write(transformxy(2, 2, bi.layerdepth, bi.destxform));     // Bottom Right
            box.Data.Write(new Vector2(bi.texpos.X + (bi.hflip ? 0 : bi.texsize.X),
                                     bi.texpos.Y + (bi.vflip ? 0 : bi.texsize.Y)));
            box.Data.Write(bi.colour);
        }

        void FlushBatchBuffer()
        {
            int boxsize;
            Array.Sort(batch, 0, batchcount);
            Texture2D currenttex = null;
            boxsize = 0;

            for (int b = 0; b < batchcount; b++)
            {
                if (batch[b].texture != currenttex)
                {
                    if (boxsize > 0)
                    {
                        device.DX_Device_Context.UnmapSubresource(vertexBuffer, 0);

                        device.DX_Device_Context.DrawIndexed(6 * boxsize, 0, 0);
                    }
                    currenttex = batch[b].texture;
                    if (device.SpriteBatchEffectPass == null)
                    {
                        if (!batch[b].isstring)
                        {
                            sprite_shader.Parameters["Texture"].SetValue(currenttex);
                            sprite_shader.Parameters["Transform"].SetValue(currenttransform);
                        }
                        else
                        {
                            string_shader.Parameters["Texture"].SetValue(currenttex);
                            string_shader.Parameters["Transform"].SetValue(currenttransform);
                        }
                    }
                    else
                    {
                        device.SpriteBatchEffectPass.ParentEffect.Parameters["Texture"].SetValue(currenttex);
                    }
                    device.RenderState.ApplyUpdates();
                    if (!batch[b].isstring)
                        dximagepass.Apply(device.DX_Device_Context);
                    else
                        dxstringpass.Apply(device.DX_Device_Context);
                    if (device.SpriteBatchEffectPass != null)
                        device.SpriteBatchEffectPass.DX_EffectPass.Apply(device.DX_Device_Context);
                    box = device.DX_Device_Context.MapSubresource(vertexBuffer, MapMode.WriteDiscard, MapFlags.None);
                    boxsize = 0;
                }
                FlushBatchItem(batch[b]);
                boxsize++;
            }
            if (boxsize > 0)
            {
                device.DX_Device_Context.UnmapSubresource(vertexBuffer, 0);
                device.DX_Device_Context.DrawIndexed(6 * boxsize, 0, 0);
            }

            batchcount = 0;
        }

        public void End()
        {
            FlushBatchBuffer();
            //if(!savedblendstatedesc.BlendOperation.Equals(0))
            //if (currentsavestatemode == SaveStateMode.SaveState)
            {
               device.RenderState.CopyToCurrentBlendState(ref savedblendstatedesc);
               device.RenderState.CurrentDepthStencilState = saveddepthstencilstatedesc;
            }

            // This is not strictly needed, but it cleans up a pixel
            // shader put into a sprite batch if its end is not called
            device.SpriteBatchEffectPass = null;
        }

        public void Draw(Texture2D texture, Rectangle destinationRectangle, Color color)
        {
            Draw(texture, new Vector2(destinationRectangle.X, destinationRectangle.Y),
                 new Vector2(destinationRectangle.Width, destinationRectangle.Height),
                 new Rectangle(0, 0, texture.Width, texture.Height), color,
                 0.0f, new Vector2(0, 0), new Vector2(1.0f, 1.0f), SpriteEffects.None, 0.0f);
        }

        public void Draw(Texture2D texture, Vector2 position, Color color)
        {
            Draw(texture, position, new Vector2(texture.Width, texture.Height),
                 new Rectangle(0, 0, texture.Width, texture.Height), color,
                 0.0f, new Vector2(0, 0), new Vector2(1.0f, 1.0f), SpriteEffects.None, 0.0f);
        }

        public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color)
        {
            Rectangle sourceR;
            if (sourceRectangle == null)
            {
                sourceR = new Rectangle(0, 0, texture.Width, texture.Height);
            }
            else
            {
                sourceR = (Rectangle)sourceRectangle;
            }
            Draw(texture, new Vector2(destinationRectangle.X, destinationRectangle.Y),
                 new Vector2(destinationRectangle.Width, destinationRectangle.Height), sourceR, color,
                 0.0f, new Vector2(0, 0), new Vector2(1.0f, 1.0f), SpriteEffects.None, 0.0f);
        }

        public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color)
        {
            Rectangle sourceR;
            if (sourceRectangle == null)
            {
                sourceR = new Rectangle(0, 0, texture.Width, texture.Height);
            }
            else
            {
                sourceR = (Rectangle)sourceRectangle;
            }
            Draw(texture, position, new Vector2(texture.Width, texture.Height), sourceR, color,
                 0.0f, new Vector2(0, 0), new Vector2(1.0f, 1.0f), SpriteEffects.None, 0.0f);
        }

        public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle,
                         Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth)
        {
            Rectangle sourceR;
            if (sourceRectangle == null)
            {
                sourceR = new Rectangle(0, 0, texture.Width, texture.Height);
            }
            else
            {
                sourceR = (Rectangle)sourceRectangle;
            }
            Draw(texture, new Vector2(destinationRectangle.X, destinationRectangle.Y),
                 new Vector2(destinationRectangle.Width, destinationRectangle.Height), sourceR, color,
                 rotation, origin, new Vector2(1.0f, 1.0f), effects, layerDepth);
        }

        public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color,
                         float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth)
        {
            Rectangle sourceR;
            if (sourceRectangle == null)
            {
                sourceR = new Rectangle(0, 0, texture.Width, texture.Height);
            }
            else
            {
                sourceR = (Rectangle)sourceRectangle;
            }
            Draw(texture, position, new Vector2(texture.Width, texture.Height), sourceR, color,
                 rotation, origin, new Vector2(scale, scale), effects, layerDepth);
        }

        public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color,
                         float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
        {
            Rectangle sourceR;
            if (sourceRectangle == null)
            {
                sourceR = new Rectangle(0, 0, texture.Width, texture.Height);
            }
            else
            {
                sourceR = (Rectangle)sourceRectangle;
            }
            Draw(texture, position, new Vector2(texture.Width, texture.Height), sourceR, color,
                 rotation, origin, scale, effects, layerDepth);
        }

        // This is the draw that actually schedules drawing - all the rest call this
        private void Draw(Texture2D texture, Vector2 position, Vector2 size, Rectangle sourceRectangle, Color color,
                          float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
        {
            bool hflip, vflip;
            hflip = (effects & SpriteEffects.FlipHorizontally) != 0;
            vflip = (effects & SpriteEffects.FlipVertically) != 0;

            // This computes destxform, texpos and texsize.
            // Along with colour and texture that should be enough to remember the sprite
            if (batchcount >= batch_buffer_size) this.FlushBatchBuffer();
            batch[batchcount].texture = texture;
            batch[batchcount].colour = new Color(color.R, color.G, color.B, color.A).PackedValue;
            batch[batchcount].layerdepth = layerDepth;
            batch[batchcount].hflip = hflip;
            batch[batchcount].vflip = vflip;
            batch[batchcount].isstring = false;

            Matrix destxform = Matrix.CreateScale(size.X * scale.X, -size.Y * scale.Y, 1);
            destxform = destxform * Matrix.CreateTranslation(-2 * origin.X * size.X * scale.X / texture.Width,
                                                              2 * origin.Y * size.Y * scale.Y / texture.Height,
                                                              0);
            if (rotation != 0) destxform = destxform * Matrix.CreateRotationZ(-rotation);
            destxform = destxform * Matrix.CreateScale(invdevwidth, invdevheight, 1);
            destxform = destxform * Matrix.CreateTranslation(2 * position.X * invdevwidth - 1,
                                                            -2 * position.Y * invdevheight + 1,
                                                             0);
            batch[batchcount].destxform = destxform;
            batch[batchcount].texpos = new Vector2((float)sourceRectangle.X / texture.Width,
                                                   (float)sourceRectangle.Y / texture.Height);
            batch[batchcount].texsize = new Vector2((float)sourceRectangle.Width / texture.Width,
                                                    (float)sourceRectangle.Height / texture.Height);

            batchcount++;
            if (currentsortmode == SpriteSortMode.Immediate)
                FlushBatchBuffer();
        }

        public void DrawString(SpriteFont spriteFont, string text, Vector2 position, Color color)
        {
            DrawString(spriteFont, text, position, color,
                       0, Vector2.Zero, new Vector2(1, 1), SpriteEffects.None, 0);
        }

        public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2 position, Color color)
        {
            DrawString(spriteFont, text.ToString(), position, color,
                       0, Vector2.Zero, new Vector2(1, 1), SpriteEffects.None, 0);
        }

        public void DrawString(SpriteFont spriteFont, string text, Vector2 position, Color color,
                               float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth)
        {
            DrawString(spriteFont, text, position, color,
                       rotation, origin, new Vector2(scale, scale), effects, layerDepth);
        }

        // This is the drawstring that actually schedules drawing - all the rest call this
        public void DrawString(SpriteFont spriteFont, string text, Vector2 position, Color color,
                               float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
        {
            float x, y;
            bool hflip, vflip;
            Vector2 size = Vector2.Zero;

            hflip = (effects & SpriteEffects.FlipHorizontally) != 0;
            vflip = (effects & SpriteEffects.FlipVertically) != 0;
            if (hflip || vflip) size = spriteFont.MeasureString(text);

            // Now display the string
            y = vflip ? (size.Y - spriteFont.LineSpacing) * scale.Y : 0;
            x = hflip ? size.X * scale.X : 0;

            foreach (char ch in text)
            {
                if (ch == '\r')
                {
                    x = hflip ? size.X * scale.X : 0;
                    continue;
                }
                if (ch == '\n')
                {
                    y += (vflip ? -spriteFont.LineSpacing : spriteFont.LineSpacing) * scale.Y;
                    continue;
                }
                int ich = (int)ch;
                if (ich < spriteFont.FirstCh)
                    ich = 0;
                else if (ich > spriteFont.LastCh)
                    ich = 0;
                else
                {
                    ich = spriteFont.CharIndex[ich - spriteFont.FirstCh];
                    if (ich < 0) ich = 0;
                }
                charinfo info = spriteFont.CharData[ich];
                Texture2D tex = spriteFont.Texture;

                if (batchcount >= batch_buffer_size) this.FlushBatchBuffer();
                batch[batchcount].texture = tex;
                batch[batchcount].colour = new Color(color.R, color.G, color.B, color.A).PackedValue;
                batch[batchcount].layerdepth = layerDepth;
                batch[batchcount].hflip = hflip;
                batch[batchcount].vflip = vflip;
                batch[batchcount].isstring = true;

                if (hflip)
                    x -= ((int)(spriteFont.CharData[ich].stride)) * scale.X + spriteFont.Spacing;

                Matrix destxform = Matrix.CreateScale(info.width * scale.X, -info.height * scale.Y, 1);
                destxform = destxform * Matrix.CreateTranslation(2 * (x - origin.X),
                                                                 2 * (origin.Y - y),
                                                                 0);
                if (rotation != 0) destxform = destxform * Matrix.CreateRotationZ(-rotation);
                destxform = destxform * Matrix.CreateScale(invdevwidth, invdevheight, 1);
                destxform = destxform * Matrix.CreateTranslation(2 * position.X * invdevwidth - 1,
                                                                -2 * position.Y * invdevheight + 1,
                                                                 0);
                batch[batchcount].destxform = destxform;
                batch[batchcount].texpos = new Vector2((float)info.x / (float)tex.Width,
                                                       (float)info.y / (float)tex.Height);
                batch[batchcount].texsize = new Vector2((float)info.width / (float)tex.Width,
                                                        (float)info.height / (float)tex.Height);

                batchcount++;
                if (currentsortmode == SpriteSortMode.Immediate)
                    FlushBatchBuffer();

                // Note: Integer truncation of stride is closer to matching XNA spacing, 
                //       but not perfect - this is still slightly too wide
                if (!hflip)
                    x += ((int)(spriteFont.CharData[ich].stride)) * scale.X + spriteFont.Spacing;
            }

        }
        public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2 position, Color color,
                               float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth)
        {
            DrawString(spriteFont, text.ToString(), position, color,
                       rotation, origin, new Vector2(scale, scale), effects, layerDepth);
        }

        public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2 position, Color color,
                               float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
        {
            DrawString(spriteFont, text.ToString(), position, color,
                       rotation, origin, scale, effects, layerDepth);
        }

        public void Dispose()
        {
            vertexBuffer.Dispose();
            indexBuffer.Dispose();
            isdisposed = true;
        }

        #region newBegin
        public void Begin(SpriteSortMode sortMode, BlendState blendState,
                          Matrix transformMatrix)//SaveStateMode stateMode
        {
            // This does not properly deal with sort mode.  I just implement
            // a single, probably incorrect version of Deferred mode.
            currentsortmode = sortMode;
            //currentsavestatemode = stateMode;
            currenttransform = transformMatrix;
            device.RenderState.CopyFromCurrentBlendState(ref savedblendstatedesc);
            saveddepthstencilstatedesc = device.RenderState.CurrentDepthStencilState;

            device.RenderState.CopyToCurrentBlendState(ref blendState.bsdesc);

            //DepthStencilStateDescription dssdesc = device.RenderState.CurrentDepthStencilState;
            //{
            //    // Premultiplied alpha is not included.  It is default in XNA4
            //    // It is: Use full source and inverse source alpha to weight destination
            //    if (blendState.Equals(BlendState.Additive))
            //    {
            //        //SpriteBlendMode.Additive // Just adds the source and destination
            //        device.RenderState.CopyToCurrentBlendState(ref additivebsdesc);

            //        // Choose depth buffer settings for transparency
            //        dssdesc.DepthWriteMask = DepthWriteMask.Zero;
            //        dssdesc.IsDepthEnabled = false;
            //    }
            //    else if (blendState.Equals(BlendState.AlphaBlend))
            //    {
            //        //SpriteBlendMode.Alpha    // Uses the source alpha value for blending
            //        //device.RenderState.CopyToCurrentBlendState(ref blendbsdesc);

            //        // Choose depth buffer settings for transparency
            //        dssdesc.DepthWriteMask = DepthWriteMask.Zero;
            //        dssdesc.IsDepthEnabled = false;
            //    }
            //    else if (blendState.Equals(BlendState.Opaque))
            //    {
            //        //SpriteBlendMode.None     // No blending
            //        device.RenderState.CopyToCurrentBlendState(ref nonebsdesc);

            //        // Choose depth buffer settings for transparency
            //        dssdesc.DepthWriteMask = DepthWriteMask.All;
            //        dssdesc.IsDepthEnabled = true;
            //    }
            //}
            //device.RenderState.CurrentDepthStencilState = dssdesc;
            device.RenderState.CurrentDepthStencilState = DepthStencilState.None.dssdesc;

            device.DX_Device_Context.InputAssembler.PrimitiveTopology = (SlimDX.Direct3D11.PrimitiveTopology.TriangleList);
            device.VertexDeclaration = vdecl;
            device.DX_Device_Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 28, 0));
            device.DX_Device_Context.InputAssembler.SetIndexBuffer(indexBuffer, SlimDX.DXGI.Format.R32_UInt, 0);

            invdevwidth = 1.0f / (float)(device.TargetWidth);
            invdevheight = 1.0f / (float)(device.TargetHeight);
        }

        public void Begin()
        {
            Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, Matrix.Identity);//SaveStateMode.None;
        }

        public void Begin(BlendState blendState)
        {
            Begin(SpriteSortMode.Deferred, blendState, Matrix.Identity);//SaveStateMode.None, Matrix.Identity);
        }

        public void Begin(SpriteSortMode sortMode, BlendState blendState)//, SaveStateMode stateMode)
        {
            Begin(sortMode, blendState, Matrix.Identity);//stateMode, Matrix.Identity);
        }
        #endregion
    }
}

/*
 *    Issues - Font doesn't draw in exactly the right size (too wide - may be integer rounding)
 *           - There is also an issue with measured string width - the extra space at start
 *             and end is all appearing at the end - noticeable with FlipHorizontal
 *           - Batch system is slow, sets shaders and parameters at the wrong times and
 *             has a sorting system which pays little or no attention to the sort parameter
 *           
 *    On the plus side - It is possible to use it with an external pixel shader, and the 
 *                       blending is working ok I think.  It also saves output merger state
 *                       when asked.
 *                       
 * 

Unimplemented stuff

        // Summary:
        //     Occurs when Dispose is called or when this object is finalized and collected
        //     by the garbage collector of the Microsoft .NET common language runtime.
        public event EventHandler Disposing;

        //
        // Summary:
        //     Immediately releases the unmanaged resources used by this object.
        //
        // Parameters:
        //   disposing:
        //     true to release both managed and unmanaged resources; false to release only
        //     unmanaged resources.
        protected virtual void Dispose(bool disposing);
*/
